NexusPi Git Node
Advertise.h copilot/add-rtnode-configuration-options (9529fa8a) Text, 20.40 KB
T8b949e// ─────────────────────────────────────────────────────────────────────────────
T8b949e// Advertise.h — Reticulum interface-discovery announcer for RTNode
T8b949e//
T8b949e// Implements the on-network interface-discovery announce protocol described
T8b949e// in the Reticulum manual: https://reticulum.network/manual/interfaces.html
T8b949e// Compatible with RNS/Discovery.py (InterfaceAnnouncer / InterfaceAnnounceHandler)
T8b949e// and LXMF/LXStamper.py (stamp generation).
T8b949e//
T8b949e// When BOUNDARY_MODE is active and the user has enabled "Advertise Device" in
T8b949e// the captive-portal configuration, this module periodically sends an
T8b949e// RNS announce on a destination with aspects "rnstransport.discovery.interface"
T8b949e// whose app_data is:
T8b949e//
T8b949e// bytes([flags]) || msgpack(info_dict) || stamp
T8b949e//
T8b949e// where info_dict contains the documented byte-id fields (interface type,
T8b949e// transport ID, latitude/longitude/height, LoRa parameters, etc.) and stamp
T8b949e// is an LXMF proof-of-work over a SHA-256/HKDF-SHA256 workblock.
T8b949e// ─────────────────────────────────────────────────────────────────────────────
Tff7b72#Tff7b72ifndef ADVERTISE_H
Tff7b72#Tff7b72define ADVERTISE_H
Tff7b72#Tff7b72ifdef BOUNDARY_MODE
Tff7b72#Tff7b72include T8b949e<Arduino.h>
Tff7b72#Tff7b72include T8b949e<Bytes.h>
Tff7b72#Tff7b72include T8b949e<Identity.h>
Tff7b72#Tff7b72include T8b949e<Destination.h>
Tff7b72#Tff7b72include T8b949e<Transport.h>
Tff7b72#Tff7b72include T8b949e<Reticulum.h>
Tff7b72#Tff7b72include T8b949e<Cryptography/HKDF.h>
Tff7b72#Tff7b72include T8b949e<Log.h>
Tff7b72#Tff7b72include T8b949e<math.h>
Tff7b72#Tff7b72include T8b949e<string.h>
Tff7b72#Tff7b72include T8b949e<stdint.h>
Tff7b72#Tff7b72include T8b949e"BoundaryMode.h"
Tff7b72#Tff7b72if defined(ESP32)
Tff7b72#Tff7b72include T8b949e<esp_task_wdt.h>
Tff7b72#Tff7b72endif
T8b949e// Externally-defined LoRa parameters (see Config.h / RNode_Firmware.ino)
Tff7b72extern Tffa657uint32_t Te6edf3lora_freqTb4b4b4;
Tff7b72extern Tffa657uint32_t Te6edf3lora_bwTb4b4b4;
Tff7b72extern Tffa657int Te6edf3lora_sfTb4b4b4;
Tff7b72extern Tffa657int Te6edf3lora_crTb4b4b4;
T8b949e// Cached node-hash hex string in RTC memory (see RNode_Firmware.ino).
Tff7b72#Tff7b72ifndef NODE_HASH_RTC_MAGIC
Tff7b72#Tff7b72define NODE_HASH_RTC_MAGIC 0x504B4841UL
Tff7b72#Tff7b72endif
Tff7b72extern Tffa657uint32_t Te6edf3rtc_node_hash_magicTb4b4b4;
Tff7b72extern Tffa657char Te6edf3rtc_node_hash_hexTb4b4b4[T79c0ff33Tb4b4b4]Tb4b4b4;
T8b949e// ─── Protocol constants (must match RNS/Discovery.py & LXMF/LXStamper.py) ───
Tff7b72#Tff7b72define ADV_FIELD_INTERFACE_TYPE 0x00
Tff7b72#Tff7b72define ADV_FIELD_TRANSPORT 0x01
Tff7b72#Tff7b72define ADV_FIELD_REACHABLE_ON 0x02
Tff7b72#Tff7b72define ADV_FIELD_LATITUDE 0x03
Tff7b72#Tff7b72define ADV_FIELD_LONGITUDE 0x04
Tff7b72#Tff7b72define ADV_FIELD_HEIGHT 0x05
Tff7b72#Tff7b72define ADV_FIELD_PORT 0x06
Tff7b72#Tff7b72define ADV_FIELD_IFAC_NETNAME 0x07
Tff7b72#Tff7b72define ADV_FIELD_IFAC_NETKEY 0x08
Tff7b72#Tff7b72define ADV_FIELD_FREQUENCY 0x09
Tff7b72#Tff7b72define ADV_FIELD_BANDWIDTH 0x0A
Tff7b72#Tff7b72define ADV_FIELD_SPREADINGFACTOR 0x0B
Tff7b72#Tff7b72define ADV_FIELD_CODINGRATE 0x0C
Tff7b72#Tff7b72define ADV_FIELD_MODULATION 0x0D
Tff7b72#Tff7b72define ADV_FIELD_CHANNEL 0x0E
Tff7b72#Tff7b72define ADV_FIELD_TRANSPORT_ID 0xFE
Tff7b72#Tff7b72define ADV_FIELD_NAME 0xFF
Tff7b72#Tff7b72define ADV_FLAG_SIGNED 0x01
Tff7b72#Tff7b72define ADV_FLAG_ENCRYPTED 0x02
T8b949e// Defaults — must match Reticulum's interface-announcer defaults so the
T8b949e// stamp validates against the on-network handler (RNS/Discovery.py).
Tff7b72#Tff7b72define ADV_DEFAULT_STAMP_COST 14
Tff7b72#Tff7b72define ADV_WORKBLOCK_EXPAND_ROUNDS 20
Tff7b72#Tff7b72define ADV_STAMP_SIZE 32 T8b949e/* SHA-256 / HASHLENGTH/8 */
T8b949e// Default announce interval matches RNS Reticulum.py's discoverable-interface
T8b949e// fallback when announce_interval is not specified (6 hours). LoRa airtime
T8b949e// is precious; this is intentionally conservative.
Tff7b72#Tff7b72define ADV_DEFAULT_ANNOUNCE_INTERVAL_S (6UL * 60UL * 60UL)
T8b949e// Initial delay after boot before the first announce — gives the radio,
T8b949e// transport and any TCP backbone time to come up.
Tff7b72#Tff7b72define ADV_INITIAL_DELAY_MS (60UL * 1000UL)
T8b949e// Privacy jitter radius. ~half a kilometre / half a mile.
Tff7b72#Tff7b72define ADV_JITTER_RADIUS_METERS 800.0
T8b949e// ─── Module state ────────────────────────────────────────────────────────────
Tff7b72static Te6edf3RNSTff7b72:Tff7b72:Te6edf3Destination Te6edf3advertise_destination Tff7b72= Tb4b4b4{Te6edf3RNSTff7b72:Tff7b72:Te6edf3TypeTff7b72:Tff7b72:Te6edf3NONETb4b4b4}Tb4b4b4;
Tff7b72static Tffa657bool Te6edf3advertise_initialised Tff7b72= Tffa657falseTb4b4b4;
Tff7b72static Tffa657bool Te6edf3advertise_first_announce Tff7b72= Tffa657trueTb4b4b4;
Tff7b72static Tffa657uint32_t Te6edf3advertise_next_run_ms Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72static Tffa657uint32_t Te6edf3advertise_announce_interval_ms Tff7b72= Te6edf3ADV_DEFAULT_ANNOUNCE_INTERVAL_S Tff7b72* T79c0ff1000ULTb4b4b4;
T8b949e// Cached stamp keyed by infohash so we only redo the proof-of-work when the
T8b949e// advertised parameters actually change (matches InterfaceAnnouncer.stamp_cache).
Tff7b72static Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3advertise_cached_infohashTb4b4b4;
Tff7b72static Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3advertise_cached_stampTb4b4b4;
T8b949e// ─── MessagePack encoder ────────────────────────────────────────────────────
T8b949e// Minimal encoder covering the types required by the discovery info dict:
T8b949e// fixmap/map16, fixstr/str8/str16, bin8/16, bool, float64, uint8/16/32, fixint.
T8b949e// Output is appended to a RNS::Bytes buffer.
Tff7b72static Tff7b72inline Tffa657void Td2a8ffadv_mp_byteTb4b4b4(Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3outTb4b4b4, Tffa657uint8_t Te6edf3bTb4b4b4) Tb4b4b4{ Te6edf3outTb4b4b4.Te6edf3appendTb4b4b4(Te6edf3bTb4b4b4)Tb4b4b4; Tb4b4b4}
Tff7b72static Tff7b72inline Tffa657void Td2a8ffadv_mp_bytesTb4b4b4(Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3outTb4b4b4, Tff7b72const Tffa657uint8_tTff7b72* Te6edf3pTb4b4b4, Tffa657size_t Te6edf3nTb4b4b4) Tb4b4b4{
Te6edf3outTb4b4b4.Te6edf3appendTb4b4b4(Te6edf3pTb4b4b4, Te6edf3nTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72static Tff7b72inline Tffa657void Td2a8ffadv_mp_be16Tb4b4b4(Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3outTb4b4b4, Tffa657uint16_t Te6edf3vTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Te6edf3v Tff7b72>Tff7b72> T79c0ff8Tb4b4b4) Tff7b72& T79c0ff0xFFTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, Te6edf3v Tff7b72& T79c0ff0xFFTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72static Tff7b72inline Tffa657void Td2a8ffadv_mp_be32Tb4b4b4(Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3outTb4b4b4, Tffa657uint32_t Te6edf3vTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Te6edf3v Tff7b72>Tff7b72> T79c0ff24Tb4b4b4) Tff7b72& T79c0ff0xFFTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Te6edf3v Tff7b72>Tff7b72> T79c0ff16Tb4b4b4) Tff7b72& T79c0ff0xFFTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Te6edf3v Tff7b72>Tff7b72> T79c0ff8Tb4b4b4) Tff7b72& T79c0ff0xFFTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, Te6edf3v Tff7b72& T79c0ff0xFFTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72static Tff7b72inline Tffa657void Td2a8ffadv_mp_map_headerTb4b4b4(Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3outTb4b4b4, Tffa657uint32_t Te6edf3nTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3n Tff7b72<Tff7b72= T79c0ff15Tb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0x80 Tff7b72| Tb4b4b4(Tffa657uint8_tTb4b4b4)Te6edf3nTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3n Tff7b72<Tff7b72= T79c0ff0xFFFFTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xdeTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_be16Tb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tffa657uint16_tTb4b4b4)Te6edf3nTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xdfTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_be32Tb4b4b4(Te6edf3outTb4b4b4, Te6edf3nTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tff7b72static Tff7b72inline Tffa657void Td2a8ffadv_mp_uintTb4b4b4(Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3outTb4b4b4, Tffa657uint64_t Te6edf3vTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3v Tff7b72<Tff7b72= T79c0ff0x7FTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tffa657uint8_tTb4b4b4)Te6edf3vTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3v Tff7b72<Tff7b72= T79c0ff0xFFTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xccTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tffa657uint8_tTb4b4b4)Te6edf3vTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3v Tff7b72<Tff7b72= T79c0ff0xFFFFTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xcdTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_be16Tb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tffa657uint16_tTb4b4b4)Te6edf3vTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3v Tff7b72<Tff7b72= T79c0ff0xFFFFFFFFULLTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xceTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_be32Tb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3vTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xcfTb4b4b4)Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff7Tb4b4b4; Te6edf3i Tff7b72>Tff7b72= T79c0ff0Tb4b4b4; Tff7b72-Tff7b72-Te6edf3iTb4b4b4) Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tffa657uint8_tTb4b4b4)Tb4b4b4(Te6edf3v Tff7b72>Tff7b72> Tb4b4b4(Te6edf3i Tff7b72* T79c0ff8Tb4b4b4)Tb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tff7b72static Tff7b72inline Tffa657void Td2a8ffadv_mp_boolTb4b4b4(Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3outTb4b4b4, Tffa657bool Te6edf3vTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, Te6edf3v Tff7b72? T79c0ff0xc3 Tff7b72: T79c0ff0xc2Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72static Tff7b72inline Tffa657void Td2a8ffadv_mp_strTb4b4b4(Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3outTb4b4b4, Tff7b72const Tffa657charTff7b72* Te6edf3sTb4b4b4) Tb4b4b4{
Tffa657size_t Te6edf3len Tff7b72= Tb4b4b4(Te6edf3s Tff7b72=Tff7b72= Tffa657nullptrTb4b4b4) Tff7b72? T79c0ff0 Tff7b72: Te6edf3strlenTb4b4b4(Te6edf3sTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3len Tff7b72<Tff7b72= T79c0ff31Tb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xa0 Tff7b72| Tb4b4b4(Tffa657uint8_tTb4b4b4)Te6edf3lenTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3len Tff7b72<Tff7b72= T79c0ff0xFFTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xd9Tb4b4b4)Tb4b4b4;
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tffa657uint8_tTb4b4b4)Te6edf3lenTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3len Tff7b72<Tff7b72= T79c0ff0xFFFFTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xdaTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_be16Tb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tffa657uint16_tTb4b4b4)Te6edf3lenTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xdbTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_be32Tb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3lenTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3len Tff7b72> T79c0ff0Tb4b4b4) Te6edf3adv_mp_bytesTb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tff7b72const Tffa657uint8_tTff7b72*Tb4b4b4)Te6edf3sTb4b4b4, Te6edf3lenTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72static Tff7b72inline Tffa657void Td2a8ffadv_mp_binTb4b4b4(Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3outTb4b4b4, Tff7b72const Tffa657uint8_tTff7b72* Te6edf3pTb4b4b4, Tffa657size_t Te6edf3lenTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3len Tff7b72<Tff7b72= T79c0ff0xFFTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xc4Tb4b4b4)Tb4b4b4;
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tffa657uint8_tTb4b4b4)Te6edf3lenTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3len Tff7b72<Tff7b72= T79c0ff0xFFFFTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xc5Tb4b4b4)Tb4b4b4;
Te6edf3adv_mp_be16Tb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tffa657uint16_tTb4b4b4)Te6edf3lenTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xc6Tb4b4b4)Tb4b4b4;
Te6edf3adv_mp_be32Tb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3lenTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3len Tff7b72> T79c0ff0Tb4b4b4) Te6edf3adv_mp_bytesTb4b4b4(Te6edf3outTb4b4b4, Te6edf3pTb4b4b4, Te6edf3lenTb4b4b4)Tb4b4b4;
Tb4b4b4}
T8b949e// IEEE-754 binary64, big-endian (msgpack float64, prefix 0xcb).
Tff7b72static Tff7b72inline Tffa657void Td2a8ffadv_mp_float64Tb4b4b4(Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3outTb4b4b4, Tffa657double Te6edf3vTb4b4b4) Tb4b4b4{
Tff7b72static_assertTb4b4b4(Tff7b72sizeofTb4b4b4(Tffa657doubleTb4b4b4) Tff7b72=Tff7b72= T79c0ff8Tb4b4b4, Ta5d6ff"Ta5d6ffmsgpack float64 requires IEEE-754 binary64Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tffa657uint8_t Te6edf3bufTb4b4b4[T79c0ff8Tb4b4b4]Tb4b4b4;
Te6edf3memcpyTb4b4b4(Te6edf3bufTb4b4b4, Tff7b72&Te6edf3vTb4b4b4, T79c0ff8Tb4b4b4)Tb4b4b4;
T8b949e// Detect host endianness — virtually always little-endian on ESP32.
Tff7b72const Tffa657uint16_t Te6edf3endian_test Tff7b72= T79c0ff1Tb4b4b4;
Tffa657bool Te6edf3host_little Tff7b72= Tb4b4b4(Tff7b72*Te6edf3reinterpret_castTff7b72<Tff7b72const Tffa657uint8_tTff7b72*Tff7b72>Tb4b4b4(Tff7b72&Te6edf3endian_testTb4b4b4)Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1Tb4b4b4;
Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, T79c0ff0xcbTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3host_littleTb4b4b4) Tb4b4b4{
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff7Tb4b4b4; Te6edf3i Tff7b72>Tff7b72= T79c0ff0Tb4b4b4; Tff7b72-Tff7b72-Te6edf3iTb4b4b4) Te6edf3adv_mp_byteTb4b4b4(Te6edf3outTb4b4b4, Te6edf3bufTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3adv_mp_bytesTb4b4b4(Te6edf3outTb4b4b4, Te6edf3bufTb4b4b4, T79c0ff8Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
T8b949e// Pack a small integer key (the field IDs are all <= 0xFF). Values <=127 fit
T8b949e// as a positive fixint; 0xFE and 0xFF need a uint8 prefix.
Tff7b72static Tff7b72inline Tffa657void Td2a8ffadv_mp_keyTb4b4b4(Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3outTb4b4b4, Tffa657uint8_t Te6edf3keyTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_uintTb4b4b4(Te6edf3outTb4b4b4, Tb4b4b4(Tffa657uint64_tTb4b4b4)Te6edf3keyTb4b4b4)Tb4b4b4;
Tb4b4b4}
T8b949e// ─── Privacy jitter (deterministic per-device) ───────────────────────────────
T8b949e// Compute a stable lat/lon offset of up to ADV_JITTER_RADIUS_METERS using the
T8b949e// node's destination hash as the seed. Stable per device → the pin doesn't
T8b949e// move around between announces, but its precise location is obscured.
Tff7b72static Tffa657void Td2a8ffadvertise_apply_jitterTb4b4b4(Tffa657doubleTff7b72& Te6edf3latTb4b4b4, Tffa657doubleTff7b72& Te6edf3lonTb4b4b4, Tff7b72const Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3seed_hashTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3seed_hashTb4b4b4.Te6edf3sizeTb4b4b4(Tb4b4b4) Tff7b72< T79c0ff8Tb4b4b4) Tff7b72returnTb4b4b4;
Tff7b72const Tffa657uint8_tTff7b72* Te6edf3h Tff7b72= Te6edf3seed_hashTb4b4b4.Te6edf3dataTb4b4b4(Tb4b4b4)Tb4b4b4;
T8b949e// Two independent uniform [0, 1) values from the hash bytes.
Tffa657uint32_t Te6edf3a Tff7b72= Tb4b4b4(Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3hTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72<Tff7b72< T79c0ff24Tb4b4b4) Tff7b72| Tb4b4b4(Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3hTb4b4b4[T79c0ff1Tb4b4b4] Tff7b72<Tff7b72< T79c0ff16Tb4b4b4) Tff7b72|
Tb4b4b4(Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3hTb4b4b4[T79c0ff2Tb4b4b4] Tff7b72<Tff7b72< T79c0ff8Tb4b4b4) Tff7b72| Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3hTb4b4b4[T79c0ff3Tb4b4b4]Tb4b4b4;
Tffa657uint32_t Te6edf3b Tff7b72= Tb4b4b4(Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3hTb4b4b4[T79c0ff4Tb4b4b4] Tff7b72<Tff7b72< T79c0ff24Tb4b4b4) Tff7b72| Tb4b4b4(Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3hTb4b4b4[T79c0ff5Tb4b4b4] Tff7b72<Tff7b72< T79c0ff16Tb4b4b4) Tff7b72|
Tb4b4b4(Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3hTb4b4b4[T79c0ff6Tb4b4b4] Tff7b72<Tff7b72< T79c0ff8Tb4b4b4) Tff7b72| Tb4b4b4(Tffa657uint32_tTb4b4b4)Te6edf3hTb4b4b4[T79c0ff7Tb4b4b4]Tb4b4b4;
Tffa657double Te6edf3u1 Tff7b72= Tb4b4b4(Tffa657doubleTb4b4b4)Te6edf3a Tff7b72/ T79c0ff4294967296.0Tb4b4b4;
Tffa657double Te6edf3u2 Tff7b72= Tb4b4b4(Tffa657doubleTb4b4b4)Te6edf3b Tff7b72/ T79c0ff4294967296.0Tb4b4b4;
T8b949e// Uniform-disk sampling to avoid bunching at the centre.
Tffa657double Te6edf3r Tff7b72= Te6edf3sqrtTb4b4b4(Te6edf3u1Tb4b4b4) Tff7b72* Te6edf3ADV_JITTER_RADIUS_METERSTb4b4b4;
Tffa657double Te6edf3theta Tff7b72= T79c0ff2.0 Tff7b72* Te6edf3M_PI Tff7b72* Te6edf3u2Tb4b4b4;
T8b949e// 1 deg latitude ≈ 111 320 m. 1 deg longitude ≈ 111 320 m * cos(lat).
Tffa657double Te6edf3dlat Tff7b72= Tb4b4b4(Te6edf3r Tff7b72* Te6edf3cosTb4b4b4(Te6edf3thetaTb4b4b4)Tb4b4b4) Tff7b72/ T79c0ff111320.0Tb4b4b4;
Tffa657double Te6edf3cos_lat Tff7b72= Te6edf3cosTb4b4b4(Te6edf3lat Tff7b72* Te6edf3M_PI Tff7b72/ T79c0ff180.0Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cos_lat Tff7b72< T79c0ff1e-6Tb4b4b4) Te6edf3cos_lat Tff7b72= T79c0ff1e-6Tb4b4b4; T8b949e// guard against the poles
Tffa657double Te6edf3dlon Tff7b72= Tb4b4b4(Te6edf3r Tff7b72* Te6edf3sinTb4b4b4(Te6edf3thetaTb4b4b4)Tb4b4b4) Tff7b72/ Tb4b4b4(T79c0ff111320.0 Tff7b72* Te6edf3cos_latTb4b4b4)Tb4b4b4;
Te6edf3lat Tff7b72+Tff7b72= Te6edf3dlatTb4b4b4;
Te6edf3lon Tff7b72+Tff7b72= Te6edf3dlonTb4b4b4;
T8b949e// Clamp back into valid ranges in the unlikely event of a pole-adjacent input.
Tff7b72if Tb4b4b4(Te6edf3lat Tff7b72> T79c0ff90.0Tb4b4b4) Te6edf3lat Tff7b72= T79c0ff90.0Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3lat Tff7b72< T79c0ff-90.0Tb4b4b4) Te6edf3lat Tff7b72= T79c0ff-90.0Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3lon Tff7b72> T79c0ff180.0Tb4b4b4) Te6edf3lon Tff7b72-Tff7b72= T79c0ff360.0Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3lon Tff7b72< T79c0ff-180.0Tb4b4b4) Te6edf3lon Tff7b72+Tff7b72= T79c0ff360.0Tb4b4b4;
Tb4b4b4}
T8b949e// ─── LXMF stamp (proof-of-work) ──────────────────────────────────────────────
T8b949e// Generates an HKDF-SHA256 workblock and finds a 32-byte stamp such that
T8b949e// SHA-256(workblock || stamp) interpreted as a big-endian integer is no
T8b949e// greater than (1 << (256 - cost)). Matches LXMF/LXStamper.py.
Tff7b72static Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Td2a8ffadvertise_stamp_workblockTb4b4b4(Tff7b72const Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3materialTb4b4b4) Tb4b4b4{
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3workblockTb4b4b4;
Tff7b72for Tb4b4b4(Tffa657int Te6edf3n Tff7b72= T79c0ff0Tb4b4b4; Te6edf3n Tff7b72< Te6edf3ADV_WORKBLOCK_EXPAND_ROUNDSTb4b4b4; Tff7b72+Tff7b72+Te6edf3nTb4b4b4) Tb4b4b4{
T8b949e// salt = full_hash(material || msgpack(n))
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3salt_inputTb4b4b4;
Te6edf3salt_inputTb4b4b4.Te6edf3appendTb4b4b4(Te6edf3materialTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_uintTb4b4b4(Te6edf3salt_inputTb4b4b4, Tb4b4b4(Tffa657uint64_tTb4b4b4)Te6edf3nTb4b4b4)Tb4b4b4;
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3salt Tff7b72= Te6edf3RNSTff7b72:Tff7b72:Te6edf3IdentityTff7b72:Tff7b72:Te6edf3full_hashTb4b4b4(Te6edf3salt_inputTb4b4b4)Tb4b4b4;
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3round Tff7b72= Te6edf3RNSTff7b72:Tff7b72:Te6edf3CryptographyTff7b72:Tff7b72:Te6edf3hkdfTb4b4b4(T79c0ff256Tb4b4b4, Te6edf3materialTb4b4b4, Te6edf3saltTb4b4b4)Tb4b4b4;
Te6edf3workblockTb4b4b4.Te6edf3appendTb4b4b4(Te6edf3roundTb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72if defined(ESP32)
Te6edf3esp_task_wdt_resetTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72endif
Tb4b4b4}
Tff7b72return Te6edf3workblockTb4b4b4;
Tb4b4b4}
T8b949e// Returns true and writes a valid stamp into "stamp_out" on success.
Tff7b72static Tffa657bool Td2a8ffadvertise_generate_stampTb4b4b4(Tff7b72const Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3workblockTb4b4b4,
Tffa657uint8_t Te6edf3costTb4b4b4,
Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3stamp_outTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3cost Tff7b72=Tff7b72= T79c0ff0 Tff7b72|Tff7b72| Te6edf3cost Tff7b72> T79c0ff32Tb4b4b4) Tff7b72return Tffa657falseTb4b4b4;
T8b949e// target = 1 << (256 - cost). We compare the leading bytes of the SHA-256
T8b949e// result against this threshold by counting leading zero bits.
Tff7b72const Tffa657uint32_t Te6edf3leading_zero_bits_required Tff7b72= Te6edf3costTb4b4b4;
Tffa657uint32_t Te6edf3round Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72while Tb4b4b4(Tffa657trueTb4b4b4) Tb4b4b4{
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3candidate Tff7b72= Te6edf3RNSTff7b72:Tff7b72:Te6edf3CryptographyTff7b72:Tff7b72:Te6edf3randomTb4b4b4(Te6edf3ADV_STAMP_SIZETb4b4b4)Tb4b4b4;
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3hash_inputTb4b4b4;
Te6edf3hash_inputTb4b4b4.Te6edf3appendTb4b4b4(Te6edf3workblockTb4b4b4)Tb4b4b4;
Te6edf3hash_inputTb4b4b4.Te6edf3appendTb4b4b4(Te6edf3candidateTb4b4b4)Tb4b4b4;
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3h Tff7b72= Te6edf3RNSTff7b72:Tff7b72:Te6edf3IdentityTff7b72:Tff7b72:Te6edf3full_hashTb4b4b4(Te6edf3hash_inputTb4b4b4)Tb4b4b4;
T8b949e// Count leading zero bits.
Tffa657uint32_t Te6edf3lz Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72const Tffa657uint8_tTff7b72* Te6edf3hp Tff7b72= Te6edf3hTb4b4b4.Te6edf3dataTb4b4b4(Tb4b4b4)Tb4b4b4;
Tffa657size_t Te6edf3hsize Tff7b72= Te6edf3hTb4b4b4.Te6edf3sizeTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657size_t Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3hsize Tff7b72&Tff7b72& Te6edf3lz Tff7b72< Te6edf3leading_zero_bits_requiredTb4b4b4; Tff7b72+Tff7b72+Te6edf3iTb4b4b4) Tb4b4b4{
Tffa657uint8_t Te6edf3byte Tff7b72= Te6edf3hpTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3lz Tff7b72+Tff7b72= T79c0ff8Tb4b4b4;
Tff7b72continueTb4b4b4;
Tb4b4b4}
Tff7b72for Tb4b4b4(Tffa657int Te6edf3bit Tff7b72= T79c0ff7Tb4b4b4; Te6edf3bit Tff7b72>Tff7b72= T79c0ff0Tb4b4b4; Tff7b72-Tff7b72-Te6edf3bitTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tb4b4b4(Te6edf3byte Tff7b72>Tff7b72> Te6edf3bitTb4b4b4) Tff7b72& T79c0ff1Tb4b4b4) Tff7b72goto Te6edf3stamp_count_doneTb4b4b4;
Te6edf3lzTff7b72+Tff7b72+Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Te6edf3stamp_count_doneTb4b4b4:
Tff7b72if Tb4b4b4(Te6edf3lz Tff7b72>Tff7b72= Te6edf3leading_zero_bits_requiredTb4b4b4) Tb4b4b4{
Te6edf3stamp_out Tff7b72= Te6edf3candidateTb4b4b4;
Tff7b72return Tffa657trueTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Tb4b4b4(Tff7b72+Tff7b72+Te6edf3round Tff7b72& T79c0ff0x3FFTb4b4b4) Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{ T8b949e// every 1024 attempts
Tff7b72#Tff7b72if defined(ESP32)
Te6edf3esp_task_wdt_resetTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72#Tff7b72endif
T8b949e// Hard cap to avoid pathological infinite loops on misconfiguration.
Tff7b72if Tb4b4b4(Te6edf3round Tff7b72> Tb4b4b4(T79c0ff1UL Tff7b72<Tff7b72< Tb4b4b4(Te6edf3cost Tff7b72+ T79c0ff6Tb4b4b4)Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72return Tffa657falseTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// ─── Build the discovery info map ────────────────────────────────────────────
T8b949e// Packs the info dict per RNS/Discovery.py::get_interface_announce_data()
T8b949e// for INTERFACE_TYPE = "RNodeInterface".
Tff7b72static Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Td2a8ffadvertise_build_infoTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3packedTb4b4b4;
T8b949e// Determine which optional fields will be included so we can write a
T8b949e// correct map header up front.
Tffa657bool Te6edf3include_ifac Tff7b72=
Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_enabled Tff7b72&Tff7b72&
Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72!Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff' Tff7b72|Tff7b72|
Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72!Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4)Tb4b4b4;
T8b949e// Required keys: INTERFACE_TYPE, TRANSPORT, TRANSPORT_ID, NAME,
T8b949e// LATITUDE, LONGITUDE, HEIGHT,
T8b949e// FREQUENCY, BANDWIDTH, SPREADINGFACTOR, CODINGRATE
T8b949e// Optional keys: IFAC_NETNAME, IFAC_NETKEY (if publish_ifac equivalent)
Tffa657uint32_t Te6edf3map_entries Tff7b72= T79c0ff11Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3include_ifacTb4b4b4) Te6edf3map_entries Tff7b72+Tff7b72= T79c0ff2Tb4b4b4;
Te6edf3adv_mp_map_headerTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3map_entriesTb4b4b4)Tb4b4b4;
T8b949e// INTERFACE_TYPE = "RNodeInterface"
Te6edf3adv_mp_keyTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3ADV_FIELD_INTERFACE_TYPETb4b4b4)Tb4b4b4;
Te6edf3adv_mp_strTb4b4b4(Te6edf3packedTb4b4b4, Ta5d6ff"Ta5d6ffRNodeInterfaceTa5d6ff"Tb4b4b4)Tb4b4b4;
T8b949e// TRANSPORT (bool) — whether transport is enabled on this node
Te6edf3adv_mp_keyTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3ADV_FIELD_TRANSPORTTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_boolTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3RNSTff7b72:Tff7b72:Te6edf3ReticulumTff7b72:Tff7b72:Te6edf3transport_enabledTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;
T8b949e// TRANSPORT_ID (bin) — RNS::Transport identity hash (truncated, 16 bytes)
Tb4b4b4{
Tff7b72const Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3tid_hash Tff7b72= Te6edf3RNSTff7b72:Tff7b72:Te6edf3TransportTff7b72:Tff7b72:Te6edf3identityTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3hashTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3adv_mp_keyTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3ADV_FIELD_TRANSPORT_IDTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_binTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3tid_hashTb4b4b4.Te6edf3dataTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3tid_hashTb4b4b4.Te6edf3sizeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}
T8b949e// NAME (str) — discovery_name. Use the user-configured node name when set,
T8b949e// otherwise fall back to a prefix of the node hash hex so each node has a
T8b949e// unique identifier visible on maps.
Tb4b4b4{
Tffa657char Te6edf3name_bufTb4b4b4[T79c0ff40Tb4b4b4]Tb4b4b4;
Tff7b72const Tffa657charTff7b72* Te6edf3adv_nameTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3node_nameTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72!Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4) Tb4b4b4{
Te6edf3adv_name Tff7b72= Te6edf3boundary_stateTb4b4b4.Te6edf3node_nameTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72const Tffa657charTff7b72* Te6edf3hex Tff7b72= Tb4b4b4(Te6edf3rtc_node_hash_magic Tff7b72=Tff7b72= Te6edf3NODE_HASH_RTC_MAGIC Tff7b72&Tff7b72& Te6edf3rtc_node_hash_hexTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72!Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4)
Tff7b72? Te6edf3rtc_node_hash_hex Tff7b72: Ta5d6ff"Ta5d6ff"Tb4b4b4;
Te6edf3snprintfTb4b4b4(Te6edf3name_bufTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3name_bufTb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffRTNode-%.8sTa5d6ff"Tb4b4b4, Te6edf3hexTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72? Te6edf3hex Tff7b72: Ta5d6ff"Ta5d6ffunknownTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3adv_name Tff7b72= Te6edf3name_bufTb4b4b4;
Tb4b4b4}
Te6edf3adv_mp_keyTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3ADV_FIELD_NAMETb4b4b4)Tb4b4b4;
Te6edf3adv_mp_strTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3adv_nameTb4b4b4)Tb4b4b4;
Tb4b4b4}
T8b949e// LATITUDE / LONGITUDE (float64) — apply optional privacy jitter.
Tb4b4b4{
Tffa657double Te6edf3adv_lat Tff7b72= Te6edf3boundary_stateTb4b4b4.Te6edf3advert_latTb4b4b4;
Tffa657double Te6edf3adv_lon Tff7b72= Te6edf3boundary_stateTb4b4b4.Te6edf3advert_lonTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3advert_jitter Tff7b72&Tff7b72& Te6edf3advertise_destinationTb4b4b4) Tb4b4b4{
Te6edf3advertise_apply_jitterTb4b4b4(Te6edf3adv_latTb4b4b4, Te6edf3adv_lonTb4b4b4, Te6edf3advertise_destinationTb4b4b4.Te6edf3hashTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3adv_mp_keyTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3ADV_FIELD_LATITUDETb4b4b4)Tb4b4b4;
Te6edf3adv_mp_float64Tb4b4b4(Te6edf3packedTb4b4b4, Te6edf3adv_latTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_keyTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3ADV_FIELD_LONGITUDETb4b4b4)Tb4b4b4;
Te6edf3adv_mp_float64Tb4b4b4(Te6edf3packedTb4b4b4, Te6edf3adv_lonTb4b4b4)Tb4b4b4;
Tb4b4b4}
T8b949e// HEIGHT (float64) — not configurable yet; default to 0 metres.
Te6edf3adv_mp_keyTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3ADV_FIELD_HEIGHTTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_float64Tb4b4b4(Te6edf3packedTb4b4b4, T79c0ff0.0Tb4b4b4)Tb4b4b4;
T8b949e// RNodeInterface-specific radio parameters (per Discovery.py:144-148).
Te6edf3adv_mp_keyTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3ADV_FIELD_FREQUENCYTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_uintTb4b4b4(Te6edf3packedTb4b4b4, Tb4b4b4(Tffa657uint64_tTb4b4b4)Te6edf3lora_freqTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_keyTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3ADV_FIELD_BANDWIDTHTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_uintTb4b4b4(Te6edf3packedTb4b4b4, Tb4b4b4(Tffa657uint64_tTb4b4b4)Te6edf3lora_bwTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_keyTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3ADV_FIELD_SPREADINGFACTORTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_uintTb4b4b4(Te6edf3packedTb4b4b4, Tb4b4b4(Tffa657uint64_tTb4b4b4)Te6edf3lora_sfTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_keyTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3ADV_FIELD_CODINGRATETb4b4b4)Tb4b4b4;
Te6edf3adv_mp_uintTb4b4b4(Te6edf3packedTb4b4b4, Tb4b4b4(Tffa657uint64_tTb4b4b4)Te6edf3lora_crTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3include_ifacTb4b4b4) Tb4b4b4{
Te6edf3adv_mp_keyTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3ADV_FIELD_IFAC_NETNAMETb4b4b4)Tb4b4b4;
Te6edf3adv_mp_strTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_netnameTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_keyTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3ADV_FIELD_IFAC_NETKEYTb4b4b4)Tb4b4b4;
Te6edf3adv_mp_strTb4b4b4(Te6edf3packedTb4b4b4, Te6edf3boundary_stateTb4b4b4.Te6edf3ifac_passphraseTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72return Te6edf3packedTb4b4b4;
Tb4b4b4}
T8b949e// ─── Send a single discovery announce ───────────────────────────────────────
Tff7b72static Tffa657void Td2a8ffadvertise_send_announceTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3advertise_destinationTb4b4b4) Tff7b72returnTb4b4b4;
Te6edf3RNSTff7b72:Tff7b72:Te6edf3verboseTb4b4b4(Ta5d6ff"Ta5d6ff[Advertise] Building discovery announceTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3packed Tff7b72= Te6edf3advertise_build_infoTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3infohash Tff7b72= Te6edf3RNSTff7b72:Tff7b72:Te6edf3IdentityTff7b72:Tff7b72:Te6edf3full_hashTb4b4b4(Te6edf3packedTb4b4b4)Tb4b4b4;
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3stampTb4b4b4;
Tffa657bool Te6edf3need_pow Tff7b72= Tffa657trueTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3advertise_cached_infohashTb4b4b4.Te6edf3sizeTb4b4b4(Tb4b4b4) Tff7b72> T79c0ff0 Tff7b72&Tff7b72&
Te6edf3advertise_cached_infohash Tff7b72=Tff7b72= Te6edf3infohash Tff7b72&Tff7b72&
Te6edf3advertise_cached_stampTb4b4b4.Te6edf3sizeTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= Te6edf3ADV_STAMP_SIZETb4b4b4) Tb4b4b4{
Te6edf3stamp Tff7b72= Te6edf3advertise_cached_stampTb4b4b4;
Te6edf3need_pow Tff7b72= Tffa657falseTb4b4b4;
Te6edf3RNSTff7b72:Tff7b72:Te6edf3verboseTb4b4b4(Ta5d6ff"Ta5d6ff[Advertise] Reusing cached stamp (info unchanged)Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3need_powTb4b4b4) Tb4b4b4{
Te6edf3RNSTff7b72:Tff7b72:Te6edf3verboseTb4b4b4(Ta5d6ff"Ta5d6ff[Advertise] Generating workblock + stamp (cost=14, this may take a few seconds)Ta5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3workblock Tff7b72= Te6edf3advertise_stamp_workblockTb4b4b4(Te6edf3infohashTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3advertise_generate_stampTb4b4b4(Te6edf3workblockTb4b4b4, Te6edf3ADV_DEFAULT_STAMP_COSTTb4b4b4, Te6edf3stampTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3RNSTff7b72:Tff7b72:Te6edf3errorTb4b4b4(Ta5d6ff"Ta5d6ff[Advertise] Stamp generation failed; skipping announceTa5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Te6edf3advertise_cached_infohash Tff7b72= Te6edf3infohashTb4b4b4;
Te6edf3advertise_cached_stamp Tff7b72= Te6edf3stampTb4b4b4;
Tb4b4b4}
T8b949e// Assemble payload: bytes([flags]) || packed || stamp
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3app_dataTb4b4b4;
Te6edf3app_dataTb4b4b4.Te6edf3appendTb4b4b4(Tb4b4b4(Tffa657uint8_tTb4b4b4)T79c0ff0x00Tb4b4b4)Tb4b4b4; T8b949e// flags: not signed, not encrypted
Te6edf3app_dataTb4b4b4.Te6edf3appendTb4b4b4(Te6edf3packedTb4b4b4)Tb4b4b4;
Te6edf3app_dataTb4b4b4.Te6edf3appendTb4b4b4(Te6edf3stampTb4b4b4)Tb4b4b4;
Te6edf3RNSTff7b72:Tff7b72:Te6edf3verboseTb4b4b4(Ta5d6ff"Ta5d6ff[Advertise] Sending interface discovery announce, payload size: Ta5d6ff" Tff7b72+
Te6edf3stdTff7b72:Tff7b72:Te6edf3to_stringTb4b4b4(Tb4b4b4(Tffa657intTb4b4b4)Te6edf3app_dataTb4b4b4.Te6edf3sizeTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72+ Ta5d6ff"Ta5d6ff bytesTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3advertise_destinationTb4b4b4.Te6edf3announceTb4b4b4(Te6edf3app_dataTb4b4b4)Tb4b4b4;
Tb4b4b4}
T8b949e// ─── Public API ─────────────────────────────────────────────────────────────
T8b949e// Initialise the advertise destination. Call once after RNS has been started
T8b949e// and Transport::identity() is available. Safe to call multiple times — only
T8b949e// the first call has any effect.
Tff7b72inline Tffa657void Td2a8ffadvertise_initTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3advertise_initialisedTb4b4b4) Tff7b72returnTb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3RNSTff7b72:Tff7b72:Te6edf3TransportTff7b72:Tff7b72:Te6edf3identityTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72returnTb4b4b4;
T8b949e// Reticulum's discovery destination uses the network identity when one is
T8b949e// configured, otherwise the transport identity. We have no concept of a
T8b949e// separate network identity in the firmware, so use the transport identity.
Te6edf3advertise_destination Tff7b72= Te6edf3RNSTff7b72:Tff7b72:Te6edf3DestinationTb4b4b4(
Te6edf3RNSTff7b72:Tff7b72:Te6edf3TransportTff7b72:Tff7b72:Te6edf3identityTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3RNSTff7b72:Tff7b72:Te6edf3TypeTff7b72:Tff7b72:Te6edf3DestinationTff7b72:Tff7b72:Te6edf3INTb4b4b4,
Te6edf3RNSTff7b72:Tff7b72:Te6edf3TypeTff7b72:Tff7b72:Te6edf3DestinationTff7b72:Tff7b72:Te6edf3SINGLETb4b4b4,
Ta5d6ff"Ta5d6ffrnstransportTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffdiscovery.interfaceTa5d6ff"
Tb4b4b4)Tb4b4b4;
Te6edf3advertise_initialised Tff7b72= Tffa657trueTb4b4b4;
Te6edf3advertise_first_announce Tff7b72= Tffa657trueTb4b4b4;
Te6edf3advertise_next_run_ms Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4) Tff7b72+ Te6edf3ADV_INITIAL_DELAY_MSTb4b4b4;
Te6edf3advertise_announce_interval_ms Tff7b72= Te6edf3ADV_DEFAULT_ANNOUNCE_INTERVAL_S Tff7b72* T79c0ff1000ULTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3boundary_stateTb4b4b4.Te6edf3advert_enabledTb4b4b4) Tb4b4b4{
Te6edf3RNSTff7b72:Tff7b72:Te6edf3infoTb4b4b4(Ta5d6ff"Ta5d6ff[Advertise] Device advertisement ENABLED — first announce in ~Ta5d6ff" Tff7b72+
Te6edf3stdTff7b72:Tff7b72:Te6edf3to_stringTb4b4b4(Te6edf3ADV_INITIAL_DELAY_MS Tff7b72/ T79c0ff1000Tb4b4b4) Tff7b72+ Ta5d6ff"Ta5d6ffsTa5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3RNSTff7b72:Tff7b72:Te6edf3verboseTb4b4b4(Ta5d6ff"Ta5d6ff[Advertise] Device advertisement disabled (configure in portal to enable)Ta5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
T8b949e// Periodic loop hook — call from the main loop().
Tff7b72inline Tffa657void Td2a8ffadvertise_loopTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3advertise_initialisedTb4b4b4) Tff7b72returnTb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3boundary_stateTb4b4b4.Te6edf3advert_enabledTb4b4b4) Tff7b72returnTb4b4b4;
Tffa657uint32_t Te6edf3now Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
T8b949e// Handle uint32 wrap-around: only treat as "due" when the unsigned
T8b949e// difference is small. millis() wraps roughly every 49 days, well after
T8b949e// any reasonable announce interval, so a wrap will at worst cause a single
T8b949e// announce to fire one cycle early.
Tffa657int32_t Te6edf3delta Tff7b72= Tb4b4b4(Tffa657int32_tTb4b4b4)Tb4b4b4(Te6edf3now Tff7b72- Te6edf3advertise_next_run_msTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3delta Tff7b72< T79c0ff0Tb4b4b4) Tff7b72returnTb4b4b4;
Te6edf3advertise_send_announceTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3advertise_first_announce Tff7b72= Tffa657falseTb4b4b4;
Te6edf3advertise_next_run_ms Tff7b72= Te6edf3now Tff7b72+ Te6edf3advertise_announce_interval_msTb4b4b4;
Tb4b4b4}
Tff7b72#Tff7b72endif T8b949e// BOUNDARY_MODE
Tff7b72#Tff7b72endif T8b949e// ADVERTISE_H
Served by rngit 1.4.2 - Generated in 0.05s